import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Set;

public class TCPServerChannel implements Runnable {
    int mPort;
    String mIPv4;
    Selector mSelector = null;
    Selector mClientSelector = null;
    ServerSocketChannel mBossChannel = null;
    Thread mThread = null;
    String mSendData = null;

    public TCPServerChannel(int port, String ipv4) {
        this.mPort = port;
        this.mIPv4 = ipv4;

        if (Init()) {
            mThread = new Thread(this);
            mThread.start();
        }
    }

    private void Close(){
        try{
            if(mBossChannel != null ) mBossChannel.close();
            if(mClientSelector != null) mClientSelector.close();
            if(mSelector != null) mSelector.close();
        }
        catch(Exception e){
            System.out.print("Close:   " + e.getMessage());
        }

    }
    private boolean Init() {
        boolean isNonError = true;
        try {
            mBossChannel = ServerSocketChannel.open();
            mClientSelector = Selector.open();
            ServerSocket serverSocket = mBossChannel.socket();
            serverSocket.bind(new InetSocketAddress(this.mIPv4, this.mPort));
            mBossChannel.configureBlocking(false);
            mSelector = Selector.open();
            mBossChannel.register(mSelector, SelectionKey.OP_ACCEPT);
        } catch (Exception e) {
            isNonError = false;
            e.printStackTrace();
            System.out.println("Init: " + e.getMessage());
            Close();
        }
        return isNonError;
    }

    @Override
    public void run() {
        try {
            while (true) {
                RunBossSocket();
                RunWorkerSocket();
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("run->" + e.getMessage());
            Close();
        }
    }

    private void RunBossSocket() throws IOException {
        int selectCount = mSelector.selectNow();
        if (selectCount <= 0) return;

        Set<SelectionKey> keys = mSelector.selectedKeys();
        if (keys.size() <= 0) return;

        Iterator<SelectionKey> iter = keys.iterator();
        while (iter.hasNext()) {
            SelectionKey key = iter.next();
            if(key.isAcceptable()){

                 ServerSocketChannel socketChannel = (ServerSocketChannel)key.channel();
                 SocketChannel  newClient = socketChannel.accept();
                System.out.println("new client connect !    :  " + newClient.socket().getRemoteSocketAddress().toString() + "  end!");
                RegisterClientSocket(newClient);
            }
            iter.remove();
        }
    }

    private void RunWorkerSocket() throws  IOException{
        int selectCount = mClientSelector.selectNow();
        if (selectCount <= 0) return;

        Set<SelectionKey> keys = mClientSelector.selectedKeys();
        if (keys.size() <= 0) return;

        Iterator<SelectionKey> iter = keys.iterator();
        while (iter.hasNext()) {
            SelectionKey key = iter.next();
            SocketChannel socketChannel = (SocketChannel) key.channel();

            if(key.isReadable()){
                ByteBuffer buf = ByteBuffer.allocate(2048);
                buf.clear();
                System.out.println(socketChannel.socket().getRemoteSocketAddress().toString() + "   socketChannel.isConnected(): " + socketChannel.isConnected() + "   key.isValid:  " + key.isValid() + "   key.isConnectable:  " + key.isConnectable());
                if(socketChannel.read(buf) <= 0){
                    key.cancel();
                    socketChannel.close();

                    Set tKeys = this.mClientSelector.keys();
                    if(tKeys.contains(socketChannel)){
                        tKeys.remove(socketChannel);
                    }else{
                        Set ttKey = this.mClientSelector.selectedKeys();
                        if(ttKey.contains(socketChannel)){
                            ttKey.remove(socketChannel);
                        }
                    }

                    System.out.println("Disconnected! hostName:  " + socketChannel.socket().getRemoteSocketAddress().toString());
                }else{
                    String str = new String(buf.array(), "utf-8").trim();
//                InetSocketAddress socketAddress = (InetSocketAddress) socketChannel.getRemoteAddress();
                    System.out.println("___Read___:" + str + "\t\t buf_str: " + buf.toString()/*+ "\nHostName :  " + socketAddress.getHostName() + "  Read !  port: " + socketAddress.getPort() + "    end! "*/);
                    mSendData = str;
                }
            }else if(key.isWritable() && mSendData != null){
                String newData = "New String to write to file..." + System.currentTimeMillis() + "  data:   " + mSendData;
                mSendData = null;
                ByteBuffer buf = ByteBuffer.allocate(2048);
                buf.clear();
                buf.put(newData.getBytes());
                buf.flip();
                socketChannel.write(buf);
                System.out.println("___Write__:" + newData + "\nHostName :  " + socketChannel.socket().getRemoteSocketAddress().toString());
            }
            iter.remove();
        }
    }

    private void RegisterClientSocket(SelectableChannel clientChannel) throws IOException  {
        clientChannel.configureBlocking(false);
        clientChannel.register(mClientSelector,SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        //mClientChannelList.add(clientChannel);
    }


    /**
     * String 转换 ByteBuffer
     * @param str
     * @return
     */
    public static ByteBuffer GetByteBuffer(String str)
    {
        return ByteBuffer.wrap(str.getBytes());
    }

    /**
     * ByteBuffer 转换 String
     * @param buffer
     * @return
     */
    public static String GetString(ByteBuffer buffer)
    {
        Charset charset = null;
        CharsetDecoder decoder = null;
        CharBuffer charBuffer = null;
        try
        {
            charset = Charset.forName("UTF-8");
            decoder = charset.newDecoder();
            // charBuffer = decoder.decode(buffer);//用这个的话，只能输出来一次结果，第二次显示为空
            charBuffer = decoder.decode(buffer.asReadOnlyBuffer());
            return charBuffer.toString();
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return "";
        }
    }
}
